package DataModel.DesignRulesManager;

import java.util.ArrayList;
import java.util.Hashtable;
import java.util.LinkedList;

import DataModel.CommonLibrary.CommonDefs;
import DataModel.CommonLibrary.CommonDefs.Layer;
import DataModel.LayoutManager.CLayoutManager;
import DataModel.LayoutManager.CRectangle;
import DataModel.LayoutManager.touple;
import ScanLineAlghorithms.RectanglesIntersection;
import VisibilityGraph.NeighboursHash;
import DataModel.DesignRulesManager.CCommonUtils;
import DataModel.DesignRulesManager.CCommonUtils.EDesignRuleType;

/**
 * @author Guy Bootman
 * Design rules checkers implementation
 */
public class CDesignRulesChecker {

	
	protected CLayoutManager manager = null;
	protected CDesignRulesManager DRManager = null;

	public CDesignRulesChecker(CLayoutManager manager,CDesignRulesManager DRManager) {
		super();
		this.manager = manager;
		this.DRManager = DRManager;
	}

	/**
	 * public  LinkedList<CDesignRulesViolation> activate() 
	 * Activates DRC of the data stored in the system and returns list of violations
	 * @return LinkedList<CDesignRulesViolation> - List of violations.
	 */
	public  LinkedList<CDesignRulesViolation> activate() {
		LinkedList<CRectangle> recs = manager.GetXSorted().GetRecsSortedList();
		LinkedList<EDesignRuleType> types = new  LinkedList<EDesignRuleType>();
		types.add(EDesignRuleType.EXTENSION);
		types.add(EDesignRuleType.OVERLAP);
		types.add(EDesignRuleType.EXACT);
		types.add(EDesignRuleType.WIDTH);
		types.add(EDesignRuleType.SPACING);
			
		return checkDR(recs,types);
	}
	/**
	 * public  LinkedList<CDesignRulesViolation> checkDR
	 * Activates check of given design rules on given rectangles
	 * @param recs rectungles to apply the checking
	 * @param types types of rules to check
	 * @return List of violations.
	 */
	public  LinkedList<CDesignRulesViolation> checkDR(LinkedList<CRectangle> recs, LinkedList<CCommonUtils.EDesignRuleType> types) {
		LinkedList<CDesignRulesViolation> violations =  new LinkedList<CDesignRulesViolation>();
		
		if ( types.contains(CCommonUtils.EDesignRuleType.EXTENSION) || types.contains(CCommonUtils.EDesignRuleType.OVERLAP)) {
			 violations.addAll(checkDRIntersecting(recs, types));
		}
		if ( types.contains(CCommonUtils.EDesignRuleType.SPACING)) {
			violations.addAll( checkDRNonIntersecting(recs,types));
		}
		if (types.contains(CCommonUtils.EDesignRuleType.WIDTH) || types.contains(CCommonUtils.EDesignRuleType.EXACT)) {
			violations.addAll(checkUnaryDR(recs, types));
		}
		
		return violations;
	}
	//Performs check of rules which involve intersecting rectangles
	private LinkedList<CDesignRulesViolation> checkDRIntersecting(LinkedList<CRectangle> recs, LinkedList<CCommonUtils.EDesignRuleType> types) {
		
		LinkedList<CDesignRulesViolation> res =  new LinkedList<CDesignRulesViolation>();
		Hashtable<Long, LinkedList<CRectangle>> recsIntersecting= new Hashtable<Long, LinkedList<CRectangle>>();
		Hashtable<Long, CRectangle> recsHash= new Hashtable<Long,CRectangle>();
		for (CRectangle r :recs )
			recsHash.put(r.getID(), r);
		RectanglesIntersection.Activate(recs, recsIntersecting);
		if (recsIntersecting.isEmpty()) {
			return res;
		}
		LinkedList<Long> keys = new LinkedList<Long>(recsIntersecting.keySet());
		ArrayList<touple> done = new ArrayList<touple>();
		for (Long key : keys) {
			LinkedList<CRectangle> rectangles = recsIntersecting.get(key);
			for (CRectangle rec : rectangles) {
				touple t = new touple(key, rec.getID());
				if (done.contains(t)) {
					continue;
				} 
				done.add(t);
				if (types.contains(CCommonUtils.EDesignRuleType.EXTENSION )) {
					CDesignRulesViolation viol = null;
					if ((viol = checkExtention(recsHash.get(key),rec)) != null) {
							res.add(viol);
					}
				}
			
				if (types.contains(CCommonUtils.EDesignRuleType.OVERLAP)) {
					CDesignRulesViolation viol = null;
					if ((viol = checkOverlap(recsHash.get(key),rec)) != null) {
							res.add(viol);
					}
				}
			}
		}
		return res;
	}

	private LinkedList<CDesignRulesViolation> checkDRNonIntersecting(LinkedList<CRectangle> recs, LinkedList<CCommonUtils.EDesignRuleType> types) {
		LinkedList<CDesignRulesViolation> res =  new LinkedList<CDesignRulesViolation>();
	    Hashtable<CommonDefs.Layer,LinkedList<CRectangle>> recsByLayer = new Hashtable<Layer, LinkedList<CRectangle>>();
		char[] dirs = {'x','y'};
	    for (CRectangle r:recs){
	    	if (!recsByLayer.containsKey(r.getLayer())) {
	    		LinkedList<CRectangle> recsInLayer = new LinkedList<CRectangle>();
	    		recsInLayer.add(r);
	    		recsByLayer.put(r.getLayer(), recsInLayer);
	    	} else {
	    		recsByLayer.get(r.getLayer()).add(r);
	    	}
	    }
	    ArrayList<CommonDefs.Layer> keys = new ArrayList<Layer>(recsByLayer.keySet());
	    
	    ArrayList<touple> done = new ArrayList<touple>();
		for (CommonDefs.Layer l :keys) { 
			LinkedList<CRectangle> layerRecs = recsByLayer.get(l);
			NeighboursHash neighboursHash = new NeighboursHash(layerRecs);
			for (CRectangle r :layerRecs ) {
				for (char dir:dirs) {
					LinkedList<CRectangle> neighbours = neighboursHash.getNeighbours(r, dir);
					if (neighbours != null) {
						for (CRectangle n : neighbours) {
							touple t = new touple(r.getID(),n.getID());
							if (done.contains(t)) continue;
							done.add(t);
							
							if (types.contains(CCommonUtils.EDesignRuleType.SPACING)){
								CDesignRulesViolation viol = null;
								if ((viol =  checkSpace(r,n,dir))!= null) {
									res.add(viol);
								}
							}					
						}
					}
				}
			}
		}
				return res;
	}

	private LinkedList<CDesignRulesViolation> checkUnaryDR(LinkedList<CRectangle> recs, LinkedList<CCommonUtils.EDesignRuleType> types) {
		LinkedList<CDesignRulesViolation> res =  new LinkedList<CDesignRulesViolation>();
		
		for (CRectangle r: recs) {
			
			if (types.contains(CCommonUtils.EDesignRuleType.WIDTH)){
				CDesignRulesViolation viol = null;
				if ((viol =  checkWidth(r))!= null) {
					res.add(viol);
				}
			}
			if (types.contains(CCommonUtils.EDesignRuleType.EXACT)){
				CDesignRulesViolation viol = null;
				if ((viol =  checkExact(r))!= null) {
					res.add(viol);
				}
			}
			
		}
		return res;
	}

	
	
	//Given two rectangles, performs extention check
	private CDesignRulesViolation checkExtention(CRectangle r1, CRectangle r2) {
		CRectangle low;
		CRectangle high;
		
		boolean violation=false;
		if (r1.getLayer().compareTo(r2.getLayer())>0 ) {
			low =r2;
			high=r1;
		} else {
			low=r1;
			high=r2;
		}
		
		double valLH  =  DRManager.GetRuleValue(low.getLayer(), high.getLayer(), EDesignRuleType.EXTENSION); 
		double valHL  =  DRManager.GetRuleValue(high.getLayer(), low.getLayer(), EDesignRuleType.EXTENSION);
		
		if (valHL == -1 && valLH == -1) {
			return null;
		}
		
		int left_ext  = (low.getLeftEdge().getStartPoint().x - high.getLeftEdge().getStartPoint().x);	
		//low extends high 
		if (left_ext <= 0 && valLH != -1 && Math.abs(left_ext)<valLH) violation = true;
		//high extend low
		if (left_ext >= 0 && valHL != -1 && Math.abs(left_ext)<valHL ) violation = true;
			
		int right_ext  = (low.getRightEdge().getEndPoint().x - high.getRightEdge().getEndPoint().x);
		//low extends high 
		if (right_ext >= 0 && valLH != -1 && Math.abs(right_ext)<valLH) violation = true;
		//high extend low
		if (right_ext <= 0 && valHL != -1 && Math.abs(right_ext)<valHL ) violation = true;
	
		
		int bottom_ext  = low.getBottomEdge().getStartPoint().y - high.getBottomEdge().getStartPoint().y;
		//low extends high 
		if (bottom_ext <= 0 && valLH != -1 && Math.abs(bottom_ext)<valLH) violation = true;
		//high extend low
		if (bottom_ext >= 0 && valHL != -1 && Math.abs(bottom_ext)<valHL ) violation = true;
		
		
		int top_ext     = low.getTopEdge().getEndPoint().y - high.getTopEdge().getEndPoint().y;
		//low extends high 
		if (top_ext >= 0 && valLH != -1 && Math.abs(top_ext)<valLH) violation = true;
		//high extend low
		if (top_ext <= 0 && valHL != -1 && Math.abs(top_ext)<valHL ) violation = true;
	
		
		if (violation) return new CDesignRulesViolation(r1,r2,EDesignRuleType.EXTENSION);
		return null;
		
	}
	//Given two rectangles, performs overlap check
	private CDesignRulesViolation checkOverlap(CRectangle r1, CRectangle r2) {
		double val = DRManager.GetRuleValue(r1.getLayer(), r2.getLayer(), EDesignRuleType.OVERLAP);
		if (val == -1 ) {
			val = DRManager.GetRuleValue(r2.getLayer(), r1.getLayer(), EDesignRuleType.OVERLAP);
			if (val == -1 ) return null;
		}
		int x1b = r1.getLeftEdge().getStartPoint().x;
		int x2b = r2.getLeftEdge().getStartPoint().x;
		int x1e = r1.getRightEdge().getStartPoint().x;
		int x2e = r2.getRightEdge().getStartPoint().x;
		
		int y1b = r1.getBottomEdge().getStartPoint().y;
		int y2b = r2.getBottomEdge().getStartPoint().y;
		int y1e = r1.getTopEdge().getStartPoint().y;
		int y2e = r2.getTopEdge().getStartPoint().y;
		
		
		int overlapX = Math.abs(Math.max(x1b, x2b) - Math.min(x1e, x2e));
		int overlapY = Math.abs(Math.max(y1b, y2b) - Math.min(y1e, y2e));						
		
		if ((overlapX >= val) && (overlapY >= val)) {
			return null;
			
		}
		return new CDesignRulesViolation(r1,r2,EDesignRuleType.OVERLAP);
	}
    //Given two rectangles, performs space check
	private CDesignRulesViolation checkSpace(CRectangle r1, CRectangle r2,char dir) {
		double val = DRManager.GetRuleValue(r1.getLayer(), r2.getLayer(), EDesignRuleType.SPACING);
		if (val == -1 ) {
			val = DRManager.GetRuleValue(r2.getLayer(), r1.getLayer(), EDesignRuleType.SPACING);
			if (val == -1 ) return null;
		}
		int x1b = r1.getLeftEdge().getStartPoint().x;
		int x2b = r2.getLeftEdge().getStartPoint().x;
		int x1e = r1.getRightEdge().getStartPoint().x;
		int x2e = r2.getRightEdge().getStartPoint().x;
		
		int y1b = r1.getBottomEdge().getStartPoint().y;
		int y2b = r2.getBottomEdge().getStartPoint().y;
		int y1e = r1.getTopEdge().getStartPoint().y;
		int y2e = r2.getTopEdge().getStartPoint().y;
		int spaceX;
		int spaceY;
		if (x1e<x2b) {
			spaceX=x2b-x1e;
		} else {
			spaceX=x1b-x2e;
		}
 	
		if (y1e<y2b) {
			spaceY=y2b-y1e;
		} else {
			spaceY=y1b-y2e;
		}
 	
		
		if ((Math.abs(spaceX)> 0 &&Math.abs(spaceX) < val && dir=='x') || (Math.abs(spaceY)> 0 &&Math.abs(spaceY) < val && dir=='y')){
			return new CDesignRulesViolation(r1,r2,EDesignRuleType.SPACING);
		}
		return null;
		  
	}
	
	//Check rectangle length
	private CDesignRulesViolation checkExact(CRectangle r1) {	
		double val = DRManager.GetRuleValue(r1.getLayer(), EDesignRuleType.EXACT);
		if (val == -1 ) return null;
		double width = r1.getWidth();
		double length = r1.getLength();
		if (width != val || length !=val) 
			return  new CDesignRulesViolation(r1,r1, EDesignRuleType.EXACT);
		return null;
		
		
	}
	
	//Check rectangles width
	private CDesignRulesViolation checkWidth(CRectangle r1) {	
		double val = DRManager.GetRuleValue(r1.getLayer(), EDesignRuleType.WIDTH);
		if (val == -1 ) return null;
		int x1b = r1.getLeftEdge().getStartPoint().x;
		int x1e = r1.getRightEdge().getStartPoint().x;
		
		int y1b = r1.getBottomEdge().getStartPoint().y;
		int y1e = r1.getTopEdge().getStartPoint().y;
	
		int rwidth;
		
		if (Math.abs(y1b-y1e)>Math.abs(x1b-x1e)) {
			rwidth = Math.abs(x1b-x1e);
		} else {
			rwidth = Math.abs(y1b-y1e);
		}
		if (rwidth<val) 
			return new CDesignRulesViolation(r1,r1,EDesignRuleType.WIDTH);
		return null;
	}
}	
